package com.cloud.common.feign;

import com.cloud.common.bean.ResultInfo;
import com.cloud.common.bean.SessionInfo;
import com.cloud.common.config.WebConfig;
import com.cloud.common.context.SessionContext;
import com.cloud.common.context.TraceContext;
import com.cloud.common.context.VersionContext;
import com.cloud.common.exception.AppException;
import com.cloud.common.mock.HttpMockInterceptor;
import com.cloud.common.rpc.RPCInvoker;
import com.cloud.common.support.BaseInterceptor;
import com.cloud.common.util.JsonUtils;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import io.seata.core.context.RootContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.*;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;

/**
 * feign拦截器
 *
 * @author fengwenjin
 */
@Slf4j
@Aspect
@Component
@ConditionalOnMissingClass("com.cloud.gateway.bean.GatewayInfo")
public class FeignInterceptor extends BaseInterceptor implements RequestInterceptor {
    @Autowired
    private HttpMockInterceptor mockInterceptor;

    @Autowired
    private RPCInvoker rpcInvoker;

    /**
     * RequestInterceptor拦截处理的回调
     *
     * @param requestTemplate
     */
    @Override
    public void apply(RequestTemplate requestTemplate) {
        if (requestTemplate != null) {
            String traceId = TraceContext.get();
            if (traceId != null) {
                requestTemplate.header(TraceContext.TRACE_ID, traceId);
            }
            SessionInfo sessionInfo = SessionContext.get();
            if (sessionInfo != null) {
                // 只传递关键信息
                SessionInfo transferSession = new SessionInfo();
                transferSession.setAppName(sessionInfo.getAppName());
                transferSession.setUserId(sessionInfo.getUserId());
                if (SessionInfo.DEBUG.equals(sessionInfo.getSecret())) {
                    transferSession.setSecret(sessionInfo.getSecret());
                }
                requestTemplate.header(SessionContext.SESSION_INFO, StringUtils.stripAll(JsonUtils.toJSONString(transferSession)));
            }
            String versionInfo = VersionContext.get();
            if (versionInfo != null) {
                requestTemplate.header(VersionContext.VERSION_INFO, versionInfo);
            }
            String xid = RootContext.getXID();
            if (StringUtils.isNotEmpty(xid)) {
                requestTemplate.header(RootContext.KEY_XID, xid);
            }
            requestTemplate.header(WebConfig.FROM_GATEWAY, Boolean.FALSE.toString());
        }
    }

    /**
     * AOP拦截处理的回调
     *
     * @param point
     * @return
     */
    @Around("@annotation(FeignInvoke)")
    public Object doFeign(ProceedingJoinPoint point) throws Throwable {
        // 获取Feign属性
        FeignAttribute feignAttribute = getFeignAttribute(point);

        // 判断是否Mock
        if (mockInterceptor.canMock(point, feignAttribute)) {
            return mockInterceptor.invokeMock(point, feignAttribute);
        }

        try {
            ResultInfo<?> resultInfo;
            if (feignAttribute.isRpc) {
                resultInfo = (ResultInfo<?>) execute(point, () -> rpcHandle(point, feignAttribute));
            } else {
                resultInfo = (ResultInfo<?>) execute(point, null);
            }
            if (resultInfo != null && resultInfo.successful()) {
                return resultInfo;
            }
            log.error("invoke service {} failure, response message >>> {}", feignAttribute.getServiceName(), resultInfo.getMessage());
        } catch (Exception e) {
            // 由于父类已经输出异常，这里无需处理
        }
        throw new AppException(ResultInfo.failure().getCode(), "内部服务调用出错");
    }

    /**
     * rpc处理
     *
     * @param point
     * @param feignAttribute
     * @return Object
     * @throws Exception
     */
    private Object rpcHandle(ProceedingJoinPoint point, FeignAttribute feignAttribute) throws Exception {
        String serviceName = feignAttribute.getServiceName();
        String requestUri = feignAttribute.getRequestUri();
        int timeout = feignAttribute.getTimeout();

        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Type returnType = methodSignature.getMethod().getGenericReturnType();
        String[] parameterNames = methodSignature.getParameterNames();
        Object[] parameterValues = point.getArgs();
        return rpcInvoker.invoke(serviceName, requestUri, timeout, parameterNames, parameterValues, returnType);
    }

    /**
     * 获取调用属性
     *
     * @param point
     * @return FeignAttribute
     */
    private FeignAttribute getFeignAttribute(ProceedingJoinPoint point) {
        // 获取调用属性
        String requestUri = null;
        FeignInvoke feignInvoke = null;
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        Annotation[] annotations = methodSignature.getMethod().getAnnotations();
        for (Annotation annotation : annotations) {
            if (annotation instanceof FeignInvoke) {
                feignInvoke = (FeignInvoke) annotation;
            } else if (annotation instanceof GetMapping) {
                requestUri = ((GetMapping) annotation).value()[0];
            } else if (annotation instanceof PostMapping) {
                requestUri = ((PostMapping) annotation).value()[0];
            } else if (annotation instanceof PutMapping) {
                requestUri = ((PutMapping) annotation).value()[0];
            } else if (annotation instanceof DeleteMapping) {
                requestUri = ((DeleteMapping) annotation).value()[0];
            } else if (annotation instanceof RequestMapping) {
                requestUri = ((RequestMapping) annotation).value()[0];
            }
        }
        if (requestUri == null) {
            throw new RuntimeException("feign client not found request uri");
        }
        if (feignInvoke == null) {
            throw new RuntimeException("feign client not found @FeignInvoke annotation");
        }
        boolean isRpc = feignInvoke.rpc();
        int timeout = feignInvoke.timeout();

        // 获取调用服务名
        Class<?> classTarget = point.getTarget().getClass().getInterfaces()[0];
        String serviceName = classTarget.getAnnotation(FeignClient.class).name();
        return new FeignAttribute(serviceName, requestUri, isRpc, timeout);
    }
}
